﻿using System;
using System.Collections;
using devtm.Aop.Copy;
using devtm.Aop.Emit;
using devtm.Aop.Extensions;
using devtm.Aop.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Reflection = System.Reflection;


namespace devtm.Aop.Convert
{
    
    public class ModuleCreator
    {


        AssemblyDefinition _assembly = null;
        ModuleDefinition _module = null;
        ConverterRuntime converter;


        public ModuleCreator(string assemblyName, string filename, Version version, ModuleKind kind, string pathResolver)
        {

            var AssemblyName = new AssemblyNameDefinition(assemblyName, version);
            
            var p = new ModuleParameters() { AssemblyResolver = new CustomAssemblyResolver(), Kind = kind };
            
            _assembly = AssemblyDefinition.CreateAssembly(AssemblyName, filename, p);
            
            _module = _assembly.MainModule;
            
            (_module.AssemblyResolver as DefaultAssemblyResolver).AddSearchDirectory(pathResolver);

            converter = new ConverterRuntime(_module);           

        }


        public TypeDefinition OverrideType(TypeReference typeBase, string Namespace, string typeName)
        {

            // gerer les methodes generic
            var methods = typeBase.GetMethods();


            Mono.Collections.Generic.Collection<TypeReference> refs = null;
            GenericInstanceType g = typeBase as GenericInstanceType;
            if (g != null && g.HasGenericArguments)
                refs = g.GenericArguments;

            var t = new TypeDefinition(Namespace, typeName, TypeAttributes.Class, _module.Import(typeBase));
            t.Attributes = TypeAttributes.Public;
            _module.Types.Add(t);

            foreach (MethodDefinition item in methods)
            {


                MethodReference m = null;

                if (typeBase.IsGenericInstance)
                {

                    m = new MethodReference(item.Name, item.ReturnType, typeBase);
                    m.HasThis = item.HasThis;
                    foreach (var parameterDefinition in item.Parameters)
                        m.Parameters.Add(parameterDefinition);

                }
                else
                {
                    m = item;
                }

                string name = item.Name;
                if (name == ".ctor")
                {

                    CopyMethod cm = new CopyMethod(item);

                    var m2 = cm.CloneMethod(typeBase, t, true);                    

                    using (CilWorker gen = new CilWorker(m2.Body))
                    {
                        gen.Clear();
                        
                        EmitExpression[] args = m2.Parameters.ConvertInEmitExpression();

                        gen.Insert
                        (
                            new EmitMethodReferenceExpression(
                                new EmitBaseReferenceExpression(), 
                                _module.Import(m)
                                )
                              .Invoke(args)
                        );

                        gen.Insert(OpCodes.Nop);
                        gen.Insert(OpCodes.Nop);
                        gen.Insert(OpCodes.Nop);

                        gen.Insert(OpCodes.Ret);

                    }

                    m2.Body.MaxStackSize = m2.Parameters.Count + 1;

                }
            }

            
            
            return t;

        }


        public TypeDefinition CreateType(string Namespace, string typeName)
        {

            var t = new TypeDefinition(Namespace, typeName, TypeAttributes.Class);
            t.Attributes = TypeAttributes.Public;
            // Ctor a faire

            _module.Types.Add(t);
            return t;

        }
      

        internal void OverrideMethod(TypeDefinition newType, MethodSign method, InterceptorContainer box)
        {

            var m1 = Get(method.Method).Resolve();

            CopyMethod cm = new CopyMethod(m1);
            MethodDefinition m2 = cm.CloneMethod(m1.DeclaringType, newType, true);

            m2.Overrides.Add(newType.Module.Import(m1));
        
            using (CilWorker gen = new CilWorker(m2.Body))
            {
                gen.Clear();
                gen.Insert(OpCodes.Nop);
                converter.GenerateContentMethod(box, m1, gen);
            }

        }


        internal void CopyFields(ImplementContainer box, TypeDefinition typeFinal)
        {
            foreach (BoxFieldReference item in box.Fields.Values)
            {
                item.Target = new FieldDefinition(item.Source.Name, item.Source.Attributes, _module.Import(item.Source.FieldType));
                typeFinal.Fields.Add(item.Target);
            }
        }


        internal void CopyMethods(ImplementContainer box, TypeDefinition typeFinal)
        {

            Hashtable fields = new Hashtable();

            foreach (var item in box.Fields)            
                fields.Add(item.Value.Source, item.Value.Target);
            

            // On creer toutes les enveloppes de methode.
            foreach (BoxMethodDefinition m in box.Methods.Values)
            {

                CopyMethod cm = new CopyMethod(m.Method);
                m.MethodTarget = cm.CloneMethod(m.Method.DeclaringType, typeFinal, true);
                
            }
            
            foreach (BoxMethodDefinition m in box.Methods.Values)
            {
                CopyMethodBody copy = new CopyMethodBody(m.Method, m.MethodTarget, fields);
                copy.CopyReferenceInstructionFromMethodBody();
            }

        }


        #region Get

        private MethodReference Get(MethodReference method)
        {          
            return _module.Import(method);
        }

        internal MethodReference Get(Reflection.MethodInfo method)
        {
            return _module.Import(method);

        }

        [System.Diagnostics.DebuggerStepThrough]
        public TypeReference Get(Type type)
        {
            return _module.Import(type);
        }

        [System.Diagnostics.DebuggerStepThrough]
        public TypeReference Get(TypeReference type)
        {
            return _module.Import(type);
        }

        #endregion


        public Reflection.Assembly Save(bool script)
        {             

            var ass = _assembly.LoadAssemblyInMemory(AppDomain.CurrentDomain, script);

            return ass;


        }


        public ModuleDefinition Module { get { return _module; } }



        public TypeDefinition TypeFinal { get; set; }
    }
}
